package com.autumn.platform.core.spring;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipException;

import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;

/* package */ class ResourceWrapperPatternResolver extends PathMatchingResourcePatternResolver {

    /* package */ Set<ResoruceWrapper> findPathMatchingResourcesWrapper(String locationPattern) throws IOException {
        String rootDirPath = determineRootDir(locationPattern);
        String subPattern = locationPattern.substring(rootDirPath.length());
        Resource[] rootDirResources = getResources(rootDirPath);
        Set<ResoruceWrapper> result = new LinkedHashSet<>(16);
        for (Resource rootDirResource : rootDirResources) {
            rootDirResource = resolveRootDirResource(rootDirResource);
            URL rootDirUrl = rootDirResource.getURL();
            if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
                result.addAll(doFindPathMatchingJarResourcesWrapper(rootDirResource, rootDirUrl, subPattern, rootDirPath));
            } else {
                result.addAll(doFindPathMatchingFileResourcesWrapper(rootDirResource, subPattern, rootDirPath));
            }
        }
        return result;
    }

    private Set<ResoruceWrapper> doFindPathMatchingJarResourcesWrapper(Resource rootDirResource, URL rootDirURL, String subPattern, String rootDirPath)
            throws IOException {

        URLConnection con = rootDirURL.openConnection();
        JarFile jarFile;
        String jarFileUrl;
        String rootEntryPath;
        boolean closeJarFile;

        if (con instanceof JarURLConnection) {
            // Should usually be the case for traditional JAR files.
            JarURLConnection jarCon = (JarURLConnection) con;
            ResourceUtils.useCachesIfNecessary(jarCon);
            jarFile = jarCon.getJarFile();
            jarFileUrl = jarCon.getJarFileURL().toExternalForm();
            JarEntry jarEntry = jarCon.getJarEntry();
            rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
            closeJarFile = !jarCon.getUseCaches();
        } else {
            // No JarURLConnection -> need to resort to URL file parsing.
            // We'll assume URLs of the format "jar:path!/entry", with the protocol
            // being arbitrary as long as following the entry format.
            // We'll also handle paths with and without leading "file:" prefix.
            String urlFile = rootDirURL.getFile();
            try {
                int separatorIndex = urlFile.indexOf(ResourceUtils.WAR_URL_SEPARATOR);
                if (separatorIndex == -1) {
                    separatorIndex = urlFile.indexOf(ResourceUtils.JAR_URL_SEPARATOR);
                }
                if (separatorIndex != -1) {
                    jarFileUrl = urlFile.substring(0, separatorIndex);
                    rootEntryPath = urlFile.substring(separatorIndex + 2); // both separators are 2 chars
                    jarFile = getJarFile(jarFileUrl);
                } else {
                    jarFile = new JarFile(urlFile);
                    jarFileUrl = urlFile;
                    rootEntryPath = "";
                }
                closeJarFile = true;
            } catch (ZipException ex) {
                return Collections.emptySet();
            }
        }

        try {
            if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) {
                // Root entry path must end with slash to allow for proper matching.
                // The Sun JRE does not return a slash here, but BEA JRockit does.
                rootEntryPath = rootEntryPath + "/";
            }
            Set<ResoruceWrapper> result = new LinkedHashSet<>(8);
            for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
                JarEntry entry = entries.nextElement();
                String entryPath = entry.getName();
                if (entryPath.startsWith(rootEntryPath)) {
                    String relativePath = entryPath.substring(rootEntryPath.length());
                    if (getPathMatcher().match(subPattern, relativePath)) {
                        result.add(new ResoruceWrapper(rootDirPath + relativePath, rootDirResource.createRelative(relativePath)));
                    }
                }
            }
            return result;
        } finally {
            if (closeJarFile) {
                jarFile.close();
            }
        }
    }

    private Set<ResoruceWrapper> doFindPathMatchingFileResourcesWrapper(Resource rootDirResource, String subPattern, String rootDirPath)
            throws IOException {
        File rootDir;
        try {
            rootDir = rootDirResource.getFile().getAbsoluteFile();
        } catch (IOException ex) {
            return Collections.emptySet();
        }
        return doFindMatchingFileSystemResourcesWrapper(rootDir, subPattern, rootDirPath);
    }

    private Set<ResoruceWrapper> doFindMatchingFileSystemResourcesWrapper(File rootDir, String subPattern, String rootDirPath) throws IOException {
        Set<FileWrapper> matchingFiles = retrieveMatchingFilesWrapper(rootDir, subPattern);
        Set<ResoruceWrapper> result = new LinkedHashSet<>(matchingFiles.size());
        for (FileWrapper file : matchingFiles) {
            result.add(new ResoruceWrapper(rootDirPath + file.relativePath, new FileSystemResource(file.file)));
        }
        return result;
    }

    private Set<FileWrapper> retrieveMatchingFilesWrapper(File rootDir, String pattern) throws IOException {
        if (!rootDir.exists()) {
            return Collections.emptySet();
        }
        if (!rootDir.isDirectory()) {
            return Collections.emptySet();
        }
        if (!rootDir.canRead()) {
            return Collections.emptySet();
        }
        String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
        if (!pattern.startsWith("/")) {
            fullPattern += "/";
        }
        fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
        Set<FileWrapper> result = new LinkedHashSet<>(8);
        doRetrieveMatchingFilesWrapper(fullPattern, rootDir, result, "");
        return result;
    }

    private void doRetrieveMatchingFilesWrapper(String fullPattern, File dir, Set<FileWrapper> result, String relativePath) throws IOException {
        File[] dirContents = dir.listFiles();
        if (dirContents == null) {
            return;
        }
        Arrays.sort(dirContents);
        for (File content : dirContents) {
            String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
            if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
                if (!content.canRead()) {
                } else {
                    doRetrieveMatchingFilesWrapper(fullPattern, content, result, relativePath + "/" + content.getName());
                }
            }
            if (getPathMatcher().match(fullPattern, currPath)) {
                result.add(new FileWrapper(relativePath + "/" + content.getName(), content));
            }
        }
    }

    /* package */ static class ResoruceWrapper {
        private String relativePath;
        private Resource resource;

        String getRelativePath() {
            return relativePath;
        }

        Resource getResource() {
            return resource;
        }

        ResoruceWrapper(String relativePath, Resource resource) {
            super();
            this.relativePath = relativePath.replaceAll("\\Q//\\E", "/");
            this.resource = resource;
        }
    }

    private static class FileWrapper {
        private String relativePath;
        private File file;

        private FileWrapper(String relativePath, File file) {
            super();
            this.relativePath = relativePath;
            this.file = file;
        }
    }
}
